package com.nightpiter.view.drawer;

import static com.nightpiter.controller.log.Logger.logProfilerBegin;
import static com.nightpiter.controller.log.Logger.logProfilerEnd;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.RectF;
import android.os.Handler;

import com.nightpiter.model.objects.Coordinates;
import com.nightpiter.view.Calculator;
import com.nightpiter.view.Calculator.Mode;
import com.nightpiter.view.graphics.Paint;
import com.nightpiter.view.graphics.Point;
import com.nightpiter.view.graphics.PointF;
import com.nightpiter.view.graphics.impl.AndroidCanvas;

/**
 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
 */
class MetroDrawerCache {

	private boolean valid = false;

	private boolean drawn = false;

	/**
	 * The drawer used to analyze model and draw it's representation to the
	 * canvas
	 */
	private MetroDrawer drawer;

	private long timeStamp = System.currentTimeMillis();

	private int redrawCount = 0;

	private PointF centerCoordF = new PointF(), minCoordF = new PointF(),
			maxCoordF = new PointF();

	/** view dimensions */
	private PointF min = new PointF(), max = new PointF(), size = new PointF();

	private PointF newMinCoordF = new PointF(), newMaxCoordF = new PointF();

	private PointF newMinF = new PointF(), newMaxF = new PointF();

	/** cache dimensions multiplier */
	private static final int CACHE_DIM = 3;

	/** cache bitmap */
	private Bitmap bitmap;

	/** wrapped canvas to pass to the drawer */
	private com.nightpiter.view.graphics.Canvas wrappedCanvas;

	/** current thread handler */
	private Handler handler = new Handler();

	/**
	 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
	 */
	private class RedrawRunnable implements Runnable {

		private Calculator calculator;

		public void run() {
			redrawCache(calculator);
			MetroDrawerWrapper.getInstance().onCacheUpdated();
		}

	}

	/** this will redraw the map only after a bit delay */
	private RedrawRunnable redrawRunnable = new RedrawRunnable();

	/** min idle time to start cache redrawing */
	private static final int REDRAW_DELAY = 700;

	private Paint paint = new Paint();

	public MetroDrawerCache() {
		paint.setARGB(255, 0, 0, 0);
		paint.setStrokeWidth(1);
		paint.setStrokeCap(Paint.Cap.ROUND);
	}

	/**
	 * 
	 * @param width
	 * @param height
	 */
	public void init(int width, int height) {
		this.size.x = width;
		this.size.y = height;

		float cacheWidth = width * CACHE_DIM;
		float cacheHeight = height * CACHE_DIM;

		this.min.x = (width - cacheWidth) / 2;
		this.min.y = (height - cacheHeight) / 2;
		this.max.x = (width + cacheWidth) / 2;
		this.max.y = (height + cacheHeight) / 2;

		this.bitmap = Bitmap.createBitmap((int) cacheWidth, (int) cacheHeight,
				Bitmap.Config.ARGB_4444);

		Canvas canvas = new Canvas(bitmap);
		// TODO
		canvas.translate((cacheWidth - width) / 2, (cacheHeight - height) / 2);

		this.wrappedCanvas = AndroidCanvas.wrap(canvas);

		invalidate();
	}

	public void setDrawer(MetroDrawer drawer, Mode mode) {
		this.drawer = drawer;
	}

	/**
	 * @param calculator
	 * @param forceRedraw
	 *            if true then the cache will be updated immediately
	 * @return
	 */
	public boolean applyCalculator(Calculator calculator, boolean forceRedraw) {
		boolean result = valid;
		PointF oldCoordinates;

		oldCoordinates = new PointF(newMinCoordF);
		calculator.revert(min, newMinCoordF);
		if (!newMinCoordF.equals(oldCoordinates)) {
			result = false;
		}
		oldCoordinates = new PointF(newMaxCoordF);
		calculator.revert(max, newMaxCoordF);
		if (!newMaxCoordF.equals(oldCoordinates)) {
			result = false;
		}

		calculator.convert(minCoordF, newMinF);
		calculator.convert(maxCoordF, newMaxF);

		if (forceRedraw) {
			handler.removeCallbacks(redrawRunnable);
			redrawCache(calculator);
		} else if (!result) {
			this.drawn = false;
			redrawRunnable.calculator = calculator;
			handler.removeCallbacks(redrawRunnable);
			handler.postDelayed(redrawRunnable, REDRAW_DELAY);
		}
		valid = true;

		return result;
	}

	/**
	 * redraw the cached image
	 */
	private void redrawCache(Calculator calculator) {
		calculator.revert(min, minCoordF);
		calculator.revert(max, maxCoordF);
		centerCoordF.x = (minCoordF.x + maxCoordF.x) / 2;
		centerCoordF.y = (minCoordF.y + maxCoordF.y) / 2;

		newMinCoordF.x = minCoordF.x;
		newMinCoordF.y = minCoordF.y;
		newMaxCoordF.x = maxCoordF.x;
		newMaxCoordF.y = maxCoordF.y;

		newMinF.x = min.x;
		newMinF.y = min.y;
		newMaxF.x = max.x;
		newMaxF.y = max.y;

		bitmap.eraseColor(Color.argb(0, 0, 0, 0));
		if (isReady()) {
			logProfilerBegin("cache redraw! (" + getDeltaTime()
					+ " millis since last; " + ++redrawCount
					+ " total redraws)");
			this.drawer.drawMetro(wrappedCanvas, calculator);
			logProfilerEnd("completed");
			this.drawn = true;
		} else {
			wrappedCanvas.drawText("drawer is not yet ready...", 0, size.y - 1,
					paint);
		}

		// debug(calculator);
	}

	@SuppressWarnings("unused")
	private void debug(Calculator calculator) {
		for (int i = 10; i < size.y - 10; i++) {
			wrappedCanvas.drawLine(10, i, size.x - 10, i, paint);
			i++;
		}

		Point point = new Point();
		calculator.convert(new Coordinates(), point);
		wrappedCanvas.drawPoint(point.x, point.y, paint);

		calculator.convert(new Coordinates(-10, -10), point);
		wrappedCanvas.drawPoint(point.x, point.y, paint);

		calculator.convert(new Coordinates(10, 10), point);
		wrappedCanvas.drawPoint(point.x, point.y, paint);

	}

	private boolean isReady() {
		return (drawer != null && drawer.isReady());
	}

	public void invalidate() {
		valid = false;
	}

	/**
	 * get time since last invalidation
	 * 
	 * @return
	 */
	public long getDeltaTime() {
		long delta = System.currentTimeMillis() - timeStamp;
		timeStamp = System.currentTimeMillis();
		return delta;
	}

	/**
	 * 
	 * @return
	 */
	public RectF getDestinationRect() {
		return new RectF(newMinF.x, newMinF.y, newMaxF.x, newMaxF.y);
	}

	/**
	 * @return the cacheBitmap
	 */
	public Bitmap getImage() {
		return bitmap;
	}

	/**
	 * @return the drawn
	 */
	public boolean isDrawn() {
		return drawn;
	}

}